﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;
using System.Text.RegularExpressions;

namespace Tzync
{
    /*********************************************************************************************************/
    /* Class to Analyze the Host System's files and its LogFile and find Conflicting/Non-Conflicting Updates */
    /*********************************************************************************************************/

    partial class Analyze
    {
        bool secondTime;
        private List<TreeNode> arrNode;
        private List<Difference> differences;
        private SystemType systemName;
        private TreeOperation treeHandler;
		private ISubject differenceSubject;
        private Filter filter;
        
        //Description     : Constructor to initialise Analyze.arrNode Tree and systemName and secondTime

        public Analyze(List<TreeNode> arrNodes, bool sT, SystemType st, ISubject subject, ref Filter f)
        {
            secondTime = sT;
            arrNode = arrNodes;            
            systemName = st;
            differences = new List<Difference>();
            treeHandler = new TreeOperation(arrNode, sT, st);
			differenceSubject = (DifferenceSubject)subject;
            filter = f;
        }

        //Description     : This function is a wrapper for findDiff() function. 
        //Pre-Conditions  : D is not NULL
        //Post-Conditions : List<Difference> differences is computed.

        public List<Difference> GetDifferences(DirectoryInfo D)
        {            
            findDiff(D, arrNode[0]);

            filter.flush();
            return differences;
        }

        //Description     : Given a system Directory, a corresponding log node, this function computes the differences
        // between the two file system trees. 
        //Pre-Conditions  : D is not NULL, N is not NULL
        //Post-Conditions : List<Difference> differences is computed.

        public void findDiff(DirectoryInfo D, TreeNode N) //throw Exception
        {
            Difference d;
            if (D == null) throw new ArgumentNullException("D");
            if (N == null) throw new ArgumentNullException("N");


            if (System.IO.Directory.Exists(D.FullName) == false)
                throw new InvalidPathException();

            if (N.getId() == 0 && systemName.CompareTo(SystemType.Host) == 0)
            {
                d = treeHandler.UpdateRootNodeA(D, N);
                if (d != null) AddDifference(d);
            }
            if (N.getId() == 0 && systemName.CompareTo(SystemType.Guest) == 0)
            {
                d = treeHandler.UpdateRootNodeB(D, N);
                if (d != null) AddDifference(d);
            }

            FileInfo[] sysFiles;
            List<int> fileChilds, folderChilds;
            DirectoryInfo[] sysDirectories;

            sysFiles = D.GetFiles();
            fileChilds = treeHandler.getFileChilds(N);
            
                try
                {
                    findFileDifference(sysFiles, fileChilds, N.getId());
                }
                //stack tracing.
                catch (Exception)
                {
                    throw;
                }                                                           

            sysDirectories = D.GetDirectories();
            try
            {
                folderChilds = treeHandler.getFolderChilds(N);
            }
            catch (Exception)
            {
                throw;
            }

              try
                {
                    findFolderDifference(sysDirectories, folderChilds, N.getId()); //Recurse here      
                }
                catch (Exception)
                {
                    throw;
                }                     
        }

        //Description     : Given a list of files and corresponding log entry TreeNodes, this function simply
        // computes the differences between them and adds to the main list of differences.         
        //Pre-Conditions  : sysFiles, fileChilds are not NULL, nodeParent is proper.
        //Post-Conditions : The list of differences maintained in this class is updated with differences in these
        // set of files.

        private void findFileDifference(FileInfo[] sysFiles, List<int> fileChilds, int nodeParent)
        {
            if (sysFiles == null) throw new ArgumentNullException("sysFiles");
            if (fileChilds == null) throw new ArgumentNullException("fileChilds");


            Pair<DirectionType, OperationType>[] fileStatus = new Pair<DirectionType, OperationType>[sysFiles.Count()];
            int sysSize = sysFiles.Count(), partnerSize = fileChilds.Count(), partnerIndex;
            List<String> nodeFileNames = new List<string>();
            bool[] partnerFileVisited = new bool[fileChilds.Count()];
            DateTimeParser dtpFile;
            int vA_f, vA_s, vB_f, vB_s;

            for (int i = 0; i < sysSize; i++)
            { fileStatus[i] = new Pair<DirectionType, OperationType>(); }
            for (int i = 0; i < partnerSize; i++)
            {
                partnerFileVisited[i] = false; nodeFileNames.Add(arrNode[fileChilds[i]].getName());                
            }

            for (int i = 0; i < sysSize; i++)
            {
                partnerIndex = nodeFileNames.IndexOf(sysFiles[i].Name);                               

                if (partnerIndex >= 0)
                {
                    vA_f = arrNode[fileChilds[partnerIndex]].getVersionA().First;
                    vA_s = arrNode[fileChilds[partnerIndex]].getVersionA().Second;
                    vB_f = arrNode[fileChilds[partnerIndex]].getVersionB().First;
                    vB_s = arrNode[fileChilds[partnerIndex]].getVersionB().Second;

                    partnerFileVisited[partnerIndex] = true;
                    dtpFile = new DateTimeParser(sysFiles[i].LastWriteTime);
                    
                    //If file is found in system, the following region checks for appropriate set of differences

                    #region CheckFileDifferencesRegion1
                    
                    //If current system is Host, Check for differences
                    if (systemName == SystemType.Host)
                    {
                        arrNode[fileChilds[partnerIndex]].setFullPathA(sysFiles[i].FullName);

                        //If system file is dirty                            
                        if (DateTime.Compare(dtpFile.GetNewDateTime(), arrNode[fileChilds[partnerIndex]].getSyncA()) != 0)
                        {
                            //If partner file is also dirty -- CONFLICTING UPDATE
                            if (vA_s != vB_s)
                            {
                                fileStatus[i].First = DirectionType.Nil;
                                //Detecting Modification(Here)-Deletion(There) Conflict
                                if (vB_f == 0 && vB_s == -1)
                                    fileStatus[i].Second = OperationType.ModDeleteConflict;
                                //Detecting Creation-Creation(First Detected there) Conflict
                                else if (vB_f == 0 && vB_s == 1 && vA_f == 0 && vA_s == 0)
                                    fileStatus[i].Second = OperationType.CreationCreationConflict;
                                //Detecting Mod-Mod Conflict
                                else fileStatus[i].Second = OperationType.ModModConflict;

                                AddDifference(new Difference(sysFiles[i], arrNode[fileChilds[partnerIndex]], fileStatus[i]));
                            }
                            //If its an update only in this system -- NON-CONFLICTING UPDATE FROM THIS SIDE
                            else
                            {
                                if (filter.isValid(sysFiles[i].Name, sysFiles[i].FullName))
                                {
                                    fileStatus[i].First = DirectionType.SystemToHD;
                                    fileStatus[i].Second = OperationType.Modification;
                                    AddDifference(new Difference(sysFiles[i], arrNode[fileChilds[partnerIndex]], fileStatus[i]));
                                }

                                else continue;
                            }
                            
                        }

                        //Else if only partner file might be dirty 
                        else
                        {
                            //Check if partner File is dirty! -- NON-CONFLICTING UPDATE FROM PARTNER SIDE
                            if (vA_s != vB_s)
                            {
                                fileStatus[i].First = DirectionType.HDToSystem;
                                if (vB_s != -1)
                                {
                                    fileStatus[i].Second = OperationType.Modification;
                                }
                                //If the file is deleted in the partner side
                                else
                                {
                                    fileStatus[i].Second = OperationType.Deletion;
                                }
                                AddDifference(new Difference(sysFiles[i], arrNode[fileChilds[partnerIndex]], fileStatus[i]));
                            }
                            //Else Identical files.
                            else
                            {
                                fileStatus[i].First = DirectionType.Nil;
                                fileStatus[i].Second = OperationType.None;
                            }
                        }
                    }

                    //If current system is Guest, Check for differences

                    else if (systemName == SystemType.Guest)
                    {
                        arrNode[fileChilds[partnerIndex]].setFullPathB(sysFiles[i].FullName);

                        //If system file is dirty                            
                        if (DateTime.Compare(dtpFile.GetNewDateTime(), arrNode[fileChilds[partnerIndex]].getSyncB()) != 0)
                        {
                            //If partner file is also dirty -- CONFLICTING UPDATE
                            if (vA_f != vB_f)
                            {
                                fileStatus[i].First = DirectionType.Nil;
                                //Detecting Modification(Here)-Deletion(There) Conflict
                                if (vA_f == -1 && vA_s == 0)
                                    fileStatus[i].Second = OperationType.ModDeleteConflict;
                                //Detecting Creation-Creation(First Detected there) Conflict
                                else if (vA_f == 1 && vA_s == 0 && vB_f == 0 && vB_s == 0)
                                    fileStatus[i].Second = OperationType.CreationCreationConflict;
                                //Detecting Mod-Mod Conflict
                                else fileStatus[i].Second = OperationType.ModModConflict;

                                AddDifference(new Difference(sysFiles[i], arrNode[fileChilds[partnerIndex]], fileStatus[i]));
                            }
                            //If its an update only in this system -- NON-CONFLICTING UPDATE FROM THIS SIDE
                            else
                            {
                                if (filter.isValid(sysFiles[i].Name, sysFiles[i].FullName))
                                {
                                    fileStatus[i].First = DirectionType.SystemToHD;
                                    fileStatus[i].Second = OperationType.Modification;
                                    AddDifference(new Difference(sysFiles[i], arrNode[fileChilds[partnerIndex]], fileStatus[i]));
                                }
                                else continue;                                
                            }                            
                        }

                        //Else if only partner file might be dirty 
                        else
                        {
                            //Check if partner File is dirty! -- NON-CONFLICTING UPDATE FROM PARTNER SIDE
                            if (vA_f != vB_f)
                            {
                                fileStatus[i].First = DirectionType.HDToSystem;
                                if (vA_f != -1)
                                {
                                    fileStatus[i].Second = OperationType.Modification;
                                }
                                //If the file is deleted in the partner side
                                else
                                {
                                    fileStatus[i].Second = OperationType.Deletion;
                                }
                                AddDifference(new Difference(sysFiles[i], arrNode[fileChilds[partnerIndex]], fileStatus[i]));
                            }
                            //Else Identical files.
                            else
                            {
                                fileStatus[i].First = DirectionType.Nil;
                                fileStatus[i].Second = OperationType.None;
                            }
                        }
                    }
                    #endregion
                }                

                //NON-CONFLICTING CREATION IN THIS SIDE
                if (partnerIndex < 0)
                {                    
                    TreeNode N;
                    try
                    {                        
                        N = treeHandler.fileUpdateArrNode(sysFiles[i], nodeParent);                        
                    }
                    //stack tracing
                    catch (Exception)
                    {                       
                        throw;
                    }

                    if (systemName == SystemType.Host && !filter.isValid(N.getName(), N.getFullPathA()))
                    {
                        //MessageBox.Show(N.getFullPathA());
                        continue;
                    }
                    if (systemName == SystemType.Guest && !filter.isValid(N.getName(), N.getFullPathB()))
                    {
                        //MessageBox.Show(N.getFullPathB());
                        continue;
                    }

                    fileStatus[i].First = DirectionType.SystemToHD; fileStatus[i].Second = OperationType.Creation;
                    AddDifference(new Difference(sysFiles[i], N, fileStatus[i]));                    
                }

            }

            //CHECK FOR (DELETIONS HERE)/(CREATIONS IN PARTNER SIDE)
            for (int i = 0; i < partnerSize; i++)
            {
                Pair<DirectionType, OperationType> f = new Pair<DirectionType, OperationType>();
                vA_f = arrNode[fileChilds[i]].getVersionA().First;
                vA_s = arrNode[fileChilds[i]].getVersionA().Second;
                vB_f = arrNode[fileChilds[i]].getVersionB().First;
                vB_s = arrNode[fileChilds[i]].getVersionB().Second;

                //If the file is not found in this system, the following function checks for appropriate set of differences

                #region CheckFileDifferencesRegion2

                if (partnerFileVisited[i] == false && fileChilds[i] != -1 && systemName==SystemType.Host)
                {                                                            
                    //NON-CONFLICTING CREATION IN THE PARTNER SIDE
                    if (vA_f==0 && vA_s==0)
                    {
                        f.First = DirectionType.HDToSystem; f.Second = OperationType.Creation;
                        String newAddress = Path.Combine(arrNode[nodeParent].getFullPathA(), arrNode[fileChilds[i]].getName());
                        arrNode[fileChilds[i]].setFullPathA(newAddress);                        

                        AddDifference(new Difference(new FileInfo(@arrNode[fileChilds[i]].getFullPathB()), arrNode[fileChilds[i]], f));
                    }
                    
                    //CONFLICTING (DELETION HERE)/UPDATE THERE
                    else if (vA_s != vB_s)
                    {
                        f.First = DirectionType.Nil;
                        
                        //If Deletion was first detected in B (If B has also deleted)
                        if (vB_f == 0 && vB_s == -1)
                        {
                            if (vA_f == -1 && vA_s == 0)
                                f.Second = OperationType.None;
                            else
                            {
                                f.Second = OperationType.DeletionDeletionConflict;
                                AddDifference(new Difference(new FileInfo(@arrNode[fileChilds[i]].getFullPathA()), arrNode[fileChilds[i]], f));
                            }
                        }
                        
                        //If B just modified 
                        else
                        {
                            if (vA_f == -1 && vA_s == 0 && vB_f != -1 && vB_s != -1)
                            {
                                f.First = DirectionType.HDToSystem;
                                f.Second = OperationType.Modification;
                            }                            
                            else
                            {
                                f.Second = OperationType.ModDeleteConflict;
                            }
                            AddDifference(new Difference(new FileInfo(@arrNode[fileChilds[i]].getFullPathA()), arrNode[fileChilds[i]], f));
                        }
                    }
                    
                    //NON-CONFLICTING DELETION HERE
                    else
                    {                        
                        
                        //Already deleted in both places long time ago
                        if (vB_f == vA_f && vB_s == vA_s && (vA_f == -1 || vA_s == -1))
                            f.Second = OperationType.None;
                        else if (filter.isValid(arrNode[fileChilds[i]].getName(), arrNode[fileChilds[i]].getFullPathA()))
                        {
                            f.Second = OperationType.Deletion;
                            f.First = DirectionType.SystemToHD;
                            AddDifference(new Difference(new FileInfo(@arrNode[fileChilds[i]].getFullPathA()), arrNode[fileChilds[i]], f));
                        }                        
                    }
                }

                else if (partnerFileVisited[i] == false && fileChilds[i] != -1 && systemName == SystemType.Guest)
                {                                       
                    //NON-CONFLICTING CREATION IN THE PARTNER SIDE
                    if (vB_f == 0 && vB_s == 0)
                    {                        
                        f.First = DirectionType.HDToSystem; f.Second = OperationType.Creation;
                        String newAddress = Path.Combine(arrNode[nodeParent].getFullPathB(), arrNode[fileChilds[i]].getName());
                        arrNode[fileChilds[i]].setFullPathB(newAddress);

                        AddDifference(new Difference(new FileInfo(@arrNode[fileChilds[i]].getFullPathB()), arrNode[fileChilds[i]], f));
                    }
                    
                    //CONFLICTING (DELETION HERE)/UPDATE THERE
                    else if (vB_f != vA_f)
                    {
                        f.First = DirectionType.Nil;
                        
                        //If Deletion was first detected in A (If A has also deleted)
                        if (vA_f == -1 && vA_s == 0)
                        {
                            if (vB_f == 0 && vB_s == -1)
                                f.Second = OperationType.None;
                            else
                            {
                                f.Second = OperationType.DeletionDeletionConflict;
                                AddDifference(new Difference(new FileInfo(@arrNode[fileChilds[i]].getFullPathB()), arrNode[fileChilds[i]], f));
                            }
                        }
                        
                        //If A just modified 
                        else
                        {
                            //If a ModDelete conflict was resolved in system A in previous sync, to set it up as 
                            //Modification
                            if (vB_f == 0 && vB_s == -1 && vA_f != -1 && vA_s != -1)
                            {
                                f.First = DirectionType.HDToSystem;
                                f.Second = OperationType.Modification;
                            }
                            
                            //Normal Mod-Delete Conflict
                            else
                            {
                                f.Second = OperationType.ModDeleteConflict;
                            }
                            AddDifference(new Difference(new FileInfo(@arrNode[fileChilds[i]].getFullPathB()), arrNode[fileChilds[i]], f));
                        }
                    }
                    //NON-CONFLICTING DELETION HERE
                    else
                    {
                        //Already deleted in both places long time ago
                        if (vB_f == vA_f && vB_s == vA_s && (vB_f == -1 || vB_s == -1))
                            f.Second = OperationType.None;
                        else if (filter.isValid(arrNode[fileChilds[i]].getName(), arrNode[fileChilds[i]].getFullPathB()))
                        {
                            f.Second = OperationType.Deletion;
                            f.First = DirectionType.SystemToHD;
                            AddDifference(new Difference(new FileInfo(@arrNode[fileChilds[i]].getFullPathB()), arrNode[fileChilds[i]], f));
                        }
                    }
                }
                #endregion
            }       

        }
        

        //Description     :  Function to add a difference to the list of Differences maintained in the class.

        private void AddDifference(Difference d)
        {
            if (d == null) throw new ArgumentNullException("d");
            differences.Add(d);
			differenceSubject.SetDifference(d);  //notify UI to display newly found conflicts
        }
        
    }

    /**********************/
    /* Class Analyze Ends */
    /**********************/
}
